//*********************************************************************************************************************
//  All Winner Tech, All Right Reserved. 2014-2015 Copyright (c)
//
//  File name   :	de_smbl.c
//
//  Description :	display engine 2.0 smbl basic function definition
//
//  History     :	2014/05/13  vito cheng  v0.1  Initial version
//*********************************************************************************************************************

#include "de_feat.h"
#include "de_smbl_type.h"
#include "de_smbl.h"
#include "de_rtmx.h"

#if defined(SUPPORT_SMBL)

#include "de_smbl_tab.h"
#define SMBL_OFST	0xB0000		//SMBL offset based on RTMX

static volatile __smbl_reg_t *smbl_dev[DEVICE_NUM];
static de_reg_blocks smbl_enable_block[DEVICE_NUM];
static de_reg_blocks smbl_ctrl_block[DEVICE_NUM];
static de_reg_blocks smbl_hist_block[DEVICE_NUM];
static de_reg_blocks smbl_csc_block[DEVICE_NUM];
static de_reg_blocks smbl_filter_block[DEVICE_NUM];
static de_reg_blocks smbl_lut_block[DEVICE_NUM];

static __smbl_status_t *g_smbl_status[DEVICE_NUM];
static unsigned int smbl_frame_cnt[DEVICE_NUM] = {0};
static unsigned short *pttab[DEVICE_NUM];   //POINTER of LGC tab

static uintptr_t smbl_hw_base[DEVICE_NUM] = {0};
static u32 PWRSAVE_PROC_THRES = 85; //when bsp_disp_lcd_get_bright() exceed PWRSAVE_PROC_THRES, STOP PWRSAVE.

/*
typedef enum
{
	SMBL_DIRTY_ENABLE    = 0x00000001,
	SMBL_DIRTY_WINDOW    = 0x00000002,
	SMBL_DIRTY_SIZE      = 0x00000004,
	SMBL_DIRTY_BL        = 0x00000008,
	SMBL_DIRTY_ALL       = 0x0000000F,
}disp_smbl_dirty_flags;


typedef struct disp_smbl_info {
	u32 enable;
	u32 window; //effect rectangle
	de_rectz size;   //display size
	u32 bl;     //backlight
	disp_smbl_dirty_flags flags;
};
*/

/*
*********************************************************************************************************
*                                           PWRSAVE_CORE
*
* Description : PoWeRSAVE alg core
*
* Arguments   : sel   <screen index>
*
* Returns         :     0
*
*Note        :    power save mode alg.  Dynamic adjust backlight and lgc gain through screen content and user backlight setting
*
*       			Add HANG-UP DETECT: When use PWRSAVE_CORE in LOW referential backlight condiction, backlight will flicker. So STOP use PWRSAVE_CORE.
*
*Date :       xx/xx/xx
**********************************************************************************************************/
static unsigned short* PWRSAVE_CORE(u32 sel)
{
	s32 i;
	u32 hist_region_num = 8;
	u32 histcnt[IEP_LH_INTERVAL_NUM];
	u32 hist[IEP_LH_INTERVAL_NUM], p95;
	u32 size=0;
	u32 min_adj_index;
	unsigned short* lgcaddr;
	u32 drc_filter_tmp=0;
	u32 backlight, thres_low, thres_high;
	static u32 printf_cnt = 0;

	backlight = g_smbl_status[sel]->backlight;

	if (backlight < PWRSAVE_PROC_THRES) {
		/* if current backlight lt PWRSAVE_PROC_THRES, close smart backlight function */
		memset(g_smbl_status[sel]->min_adj_index_hist, 255, sizeof(u8)*IEP_LH_PWRSV_NUM);
		lgcaddr = (unsigned short*)((uintptr_t)pttab[sel] + ((128-1)<<9));

		g_smbl_status[sel]->dimming = 256;
	}	else {
		/* if current backlight ge PWRSAVE_PROC_THRES, open smart backlight function */
		p95=0;

		hist_region_num = (hist_region_num>8)? 8 : IEP_LH_INTERVAL_NUM;

		//read histogram result
		de_smbl_get_hist(sel, histcnt);

		//for (i=0; i<IEP_LH_INTERVAL_NUM; i++) {
		//	size += histcnt[i];
		//}
		//size = (size==0) ? 1 : size;


		//calculate some var
		//hist[0] = (histcnt[0]*100)/size;
		//for (i = 1; i < hist_region_num; i++) {
		//	hist[i] = (histcnt[i]*100)/size + hist[i-1];
		//}

		size = g_smbl_status[sel]->size;

		//calculate some var
		hist[0] = (histcnt[0])/size;
		for (i = 1; i < hist_region_num; i++) {
			hist[i] = (histcnt[i])/size + hist[i-1];
		}

#if 0
		for (i = 0; i< hist_region_num; i++) {
			if (hist[i] >= 95) {
				p95 = hist_thres_pwrsv[i];
				break;
			}
		}

		/* sometime, hist[hist_region_num - 1] may less than 95 due to integer calc */
		if (i == hist_region_num) {
			p95 = hist_thres_pwrsv[7];
		}
#else //fix bug of some thing bright appear in a dark background
		for (i = hist_region_num-1; i >= 0; i--)
		{
			if (hist[i] < 95) {
				break;
			}
		}

		/* sometime, hist[hist_region_num - 1] may less than 95 due to integer calc */
		if (i == hist_region_num-1)
		{
			p95 = hist_thres_pwrsv[7];
		}
		else if (i == 0)
		{
			p95 = hist_thres_pwrsv[0];
		}
		else
		{
			p95 = hist_thres_pwrsv[i+1];
		}
#endif
		min_adj_index = p95;

		//__inf("min_adj_index: %d\n", min_adj_index);
		for (i = 0; i <IEP_LH_PWRSV_NUM - 1; i++) {
			g_smbl_status[sel]->min_adj_index_hist[i] = g_smbl_status[sel]->min_adj_index_hist[i+1];
		}
		g_smbl_status[sel]->min_adj_index_hist[IEP_LH_PWRSV_NUM-1] = min_adj_index;

		for (i = 0; i <IEP_LH_PWRSV_NUM; i++) {
			//drc_filter_total += drc_filter[i];
			drc_filter_tmp += drc_filter[i]*g_smbl_status[sel]->min_adj_index_hist[i];
		}
		//min_adj_index = drc_filter_tmp/drc_filter_total;
		min_adj_index = drc_filter_tmp>>10;

		thres_low = PWRSAVE_PROC_THRES;
		thres_high = PWRSAVE_PROC_THRES + ((255 - PWRSAVE_PROC_THRES)/5);
		if (backlight < thres_high) {
			min_adj_index = min_adj_index + (255 - min_adj_index)*(thres_high - backlight)
			    / (thres_high - thres_low);
		}
		min_adj_index = (min_adj_index >= 255)?255:((min_adj_index<hist_thres_pwrsv[0])?hist_thres_pwrsv[0]:min_adj_index);

		g_smbl_status[sel]->dimming = min_adj_index + 1;

		lgcaddr = (unsigned short*)((uintptr_t)pttab[sel] + ((min_adj_index - 128)<<9));

		if (printf_cnt == 600)	{
			__inf("save backlight power: %d percent\n", (256 - (u32)min_adj_index)*100 / 256);
			printf_cnt = 0;
		}	else {
			printf_cnt++;
		}

	}
	return lgcaddr;
}

int de_smbl_tasklet(unsigned int sel)
{
	unsigned short *lut;
	if (g_smbl_status[sel]->IsEnable  && ((SMBL_FRAME_MASK==(smbl_frame_cnt[sel]%2)) || (SMBL_FRAME_MASK==0x2)))
	{
		if (g_smbl_status[sel]->Runtime > 0)
		{
			//POWER SAVE ALG
			lut = (unsigned short *)PWRSAVE_CORE(sel);
		}
		else
		{
			lut = (unsigned short *)pwrsv_lgc_tab[128-1];
		}

		de_smbl_set_lut(sel, lut);

		if (g_smbl_status[sel]->Runtime == 0)
		{
			g_smbl_status[sel]->Runtime ++;
		}
	}
	smbl_frame_cnt[sel]++;

	return 0;
}

int de_smbl_apply(unsigned int sel, struct disp_smbl_info *info)
{
	__inf("sel%d, en=%d, win=<%d,%d,%d,%d>\n", sel, info->enable,
	info->window.x, info->window.y, info->window.width, info->window.height);
	g_smbl_status[sel]->backlight = info->backlight;
	if (info->flags & SMBL_DIRTY_ENABLE)
	{
		g_smbl_status[sel]->IsEnable = info->enable;
		de_smbl_enable(sel, g_smbl_status[sel]->IsEnable);

		if (g_smbl_status[sel]->IsEnable)
		{
			g_smbl_status[sel]->Runtime = 0;
			memset(g_smbl_status[sel]->min_adj_index_hist, 255, sizeof(u8)*IEP_LH_PWRSV_NUM);
			de_smbl_set_para(sel, info->size.width, info->size.height);
		}
		else
		{
		}
	}
	g_smbl_status[sel]->backlight = info->backlight;

	if (info->flags & SMBL_DIRTY_WINDOW)
	{
		de_smbl_set_window(sel, 1, info->window);
	}

	return 0;

}

int de_smbl_update_regs(unsigned int sel)
{
	unsigned int reg_val;

	if (smbl_ctrl_block[sel].dirty == 0x1){
		memcpy((void *)smbl_ctrl_block[sel].off,smbl_ctrl_block[sel].val,smbl_ctrl_block[sel].size);
		smbl_ctrl_block[sel].dirty = 0x0;
	}

	if (smbl_enable_block[sel].dirty == 0x1) {
		reg_val = readl((void __iomem *)(smbl_enable_block[sel].off));
		reg_val &= 0x2;
		reg_val |= *((volatile u32*)smbl_enable_block[sel].val);
		writel(reg_val, (void __iomem *)(smbl_enable_block[sel].off));
        smbl_enable_block[sel].dirty = 0;
	}
	return 0;
}

int de_smbl_set_reg_base(unsigned int sel, void *base)
{
	smbl_dev[sel] = (__smbl_reg_t *)base;

	return 0;
}

int de_smbl_init(unsigned int sel, uintptr_t reg_base)
{
	uintptr_t base;
	void *memory;
	unsigned int lcdgamma;
	int  value = 1;
	char primary_key[20];
	int  ret;

	base = reg_base + (sel+1)*0x00100000 + SMBL_OFST;
	smbl_hw_base[sel] = base;

	__inf("sel %d, smbl_base=0x%p\n", sel, (void*)base);

	memory = (void *)kmalloc(sizeof(__smbl_reg_t), GFP_KERNEL | __GFP_ZERO);
	if (NULL == memory) {
		__wrn("malloc smbl[%d] memory fail! size=0x%x\n", sel, (unsigned int)sizeof(__smbl_reg_t));
		return -1;
	}

	smbl_enable_block[sel].off			= base;
	smbl_enable_block[sel].val			= memory;
	smbl_enable_block[sel].size			= 0x4;
	smbl_enable_block[sel].dirty 		= 0;

	smbl_ctrl_block[sel].off			= base + 0x4;
	smbl_ctrl_block[sel].val			= memory + 0x4;
	smbl_ctrl_block[sel].size			= 0x38;
	smbl_ctrl_block[sel].dirty 			= 0;

	smbl_hist_block[sel].off			= base + 0x60;
	smbl_hist_block[sel].val			= memory + 0x60;
	smbl_hist_block[sel].size			= 0x20;
	smbl_hist_block[sel].dirty 			= 0;

	smbl_csc_block[sel].off				= base + 0xc0;
	smbl_csc_block[sel].val				= memory + 0xc0;
	smbl_csc_block[sel].size			= 0x30;
	smbl_csc_block[sel].dirty 			= 0;

	smbl_filter_block[sel].off			= base + 0xf0;
	smbl_filter_block[sel].val			= memory + 0xf0;
	smbl_filter_block[sel].size			= 0x110;
	smbl_filter_block[sel].dirty 		= 0;

	smbl_lut_block[sel].off				= base + 0x200;
	smbl_lut_block[sel].val				= memory + 0x200;
	smbl_lut_block[sel].size			= 0x200;
	smbl_lut_block[sel].dirty 			= 0;

	de_smbl_set_reg_base(sel, memory);

 	g_smbl_status[sel] = (void *)kmalloc(sizeof(__smbl_status_t), GFP_KERNEL | __GFP_ZERO);
	if (NULL == g_smbl_status[sel]) {
		__wrn("malloc g_smbl_status[%d] memory fail! size=0x%x\n", sel, (unsigned int)sizeof(__smbl_status_t));
		return -1;
	}

	g_smbl_status[sel]->IsEnable = 0;
	g_smbl_status[sel]->Runtime = 0;
	g_smbl_status[sel]->dimming = 256;

	sprintf(primary_key, "lcd%d", sel);

	ret = disp_sys_script_get_item(primary_key, "lcdgamma4iep", &value, 1);
	if (ret != 1)	{
		lcdgamma = 6; //default gamma = 2.2;
	} else {
		//DE_INF("lcdgamma4iep for lcd%d = %d.\n", sel, value);
		if (value > 30 || value < 10) {
			//DE_WRN("lcdgamma4iep for lcd%d too small or too large. default value 22 will be set. please set it between 10 and 30 to make it valid.\n",sel);
			lcdgamma = 6;//default gamma = 2.2;
		}	else {
			lcdgamma = (value - 10)/2;
		}
	}

	pttab[sel] = pwrsv_lgc_tab[128*lcdgamma];

	ret = disp_sys_script_get_item(primary_key, "smartbl_low_limit", &value, 1);
	if (ret != 1) {
		//DE_INF("smartbl_low_limit for lcd%d not exist.\n", sel);
	}	else {
		//DE_INF("smartbl_low_limit for lcd%d = %d.\n", sel, value);
		if (value > 255 || value < 20)	{
			//DE_INF("smartbl_low_limit for lcd%d too small or too large. default value 85 will be set. please set it between 20 and 255 to make it valid.\n",sel);
		}	else {
			PWRSAVE_PROC_THRES = value;
		}
	}
	sprintf(primary_key, "lcd%d", sel);
	ret = disp_sys_script_get_item(primary_key, "lcd_backlight", &value, 1);
	if (1 == ret)
		g_smbl_status[sel]->backlight = value;

	return 0;
}

//*********************************************************************************************************************
// function       : de_smbl_enable(unsigned int sel, unsigned int en)
// description    : enable/disable smbl
// parameters     :
//                  sel		<rtmx select>
//                  en		<enable: 0-diable; 1-enable>
// return         :
//                  success
//*********************************************************************************************************************
int de_smbl_enable(unsigned int sel, unsigned int en)
{

	smbl_dev[sel]->gnectl.bits.en = en;
	smbl_enable_block[sel].dirty 		 = 1;
	return 0;
}

//*********************************************************************************************************************
// function       : de_smbl_set_window(unsigned int sel, unsigned int win_enable, de_rect window)
// description    : set smbl window
// parameters     :
//                  sel		<rtmx select>
//                  win_enable	<enable: 0-window mode diable; 1-window mode enable>
//					window	<window rectangle>
// return         :
//                  success
//*********************************************************************************************************************
int de_smbl_set_window(unsigned int sel, unsigned int win_enable, struct disp_rect window)
{
	smbl_dev[sel]->drcctl.bits.win_en = win_enable;

	if (win_enable)
	{
		smbl_dev[sel]->drc_wp0.bits.win_left = window.x;
		smbl_dev[sel]->drc_wp0.bits.win_top = window.y;
		smbl_dev[sel]->drc_wp1.bits.win_right = window.x + window.width - 1;
		smbl_dev[sel]->drc_wp1.bits.win_bottom = window.y + window.height - 1;
	}
	smbl_ctrl_block[sel].dirty 		 = 1;
	return 0;
}

//*********************************************************************************************************************
// function       : de_smbl_set_para(unsigned int sel)
// description    : set smbl para
// parameters     :
//                  sel		<rtmx select>
// return         :
//                  success
//*********************************************************************************************************************
int de_smbl_set_para(unsigned int sel, unsigned int width, unsigned int height)
{

	smbl_dev[sel]->gnectl.bits.mod = 2;
	smbl_dev[sel]->drcsize.dwval = (height-1)<<16 | (width-1);
	smbl_dev[sel]->drcctl.bits.hsv_en = 1; //fix hsv mode
	smbl_dev[sel]->drcctl.bits.db_en = 1;
	smbl_dev[sel]->drc_set.dwval = 0;
	smbl_dev[sel]->lhctl.dwval = 0;

	smbl_dev[sel]->lhthr0.dwval = (hist_thres_drc[3]<<24) | (hist_thres_drc[2]<<16) | (hist_thres_drc[1]<<8) | (hist_thres_drc[0]);
	smbl_dev[sel]->lhthr1.dwval = (hist_thres_drc[6]<<16) | (hist_thres_drc[5]<<8) | (hist_thres_drc[4]);

	//out_csc coeff
	memcpy((void*)smbl_csc_block[sel].off, (unsigned char *)csc_bypass_coeff, sizeof(unsigned int)*12);

	//filter coeff
	memcpy((void*)smbl_filter_block[sel].off, (unsigned char *)smbl_filter_coeff, sizeof(unsigned char)*272);

	smbl_enable_block[sel].dirty = 1;
	smbl_ctrl_block[sel].dirty = 1;
	smbl_csc_block[sel].dirty = 0;
	smbl_filter_block[sel].dirty = 0;

	g_smbl_status[sel]->size = width*height/100;

	return 0;
}

int de_smbl_set_lut(unsigned int sel, unsigned short *lut)
{
	uintptr_t base;
	unsigned int reg_val;
	base = smbl_hw_base[sel];

	//set lut to smbl lut SRAM
	memcpy((void*)smbl_lut_block[sel].off, (unsigned char *)lut, sizeof(unsigned short)*256);
	reg_val = readl((void __iomem *)(base));
	reg_val |= 0x00000010;
	writel(reg_val, (void __iomem *)(base));

	return 0;
}

int de_smbl_get_hist(unsigned int sel, unsigned int *cnt)
{
#if 0
	int i;
	//for_test
	//FIXME
	for (i=0; i<IEP_LH_INTERVAL_NUM/2; i++) {
		cnt[i] = 0x17700;
	}
	for (i=IEP_LH_INTERVAL_NUM/2; i<IEP_LH_INTERVAL_NUM; i++) {
		cnt[i] = 0x0;
	}
	return 0;
#endif

	//Read histogram
	memcpy((unsigned char *)cnt, (unsigned char *)smbl_hist_block[sel].off, sizeof(unsigned int)*IEP_LH_INTERVAL_NUM);

	return 0;
}

int de_smbl_get_status(unsigned int sel)
{
	return g_smbl_status[sel]->dimming;
}

#else

int de_smbl_set_reg_base(unsigned int sel, void *base)
{
	return 0;
}

int de_smbl_update_regs(unsigned int sel)
{
	return 0;
}

int de_smbl_tasklet(unsigned int sel)
{
	return 0;
}

int de_smbl_apply(unsigned int sel, struct disp_smbl_info *info)
{
	return 0;
}

int de_smbl_sync(unsigned int sel)
{
	return 0;
}

int de_smbl_init(unsigned int sel, uintptr_t reg_base)
{
	return 0;
}

int de_smbl_enable(unsigned int sel, unsigned int en)
{
	return 0;
}

int de_smbl_set_window(unsigned int sel, unsigned int win_enable, struct disp_rect window)
{
	return 0;
}

int de_smbl_set_para(unsigned int sel, unsigned int width, unsigned int height)
{
	return 0;
}

int de_smbl_set_lut(unsigned int sel, unsigned short *lut)
{
	return 0;
}

int de_smbl_get_hist(unsigned int sel, unsigned int *cnt)
{
	return 0;
}
int de_smbl_get_status(unsigned int sel)
{
	return 0;
}
#endif
